<?php
/**
 * Concentre Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Concentre
 * @package    Concentre_Daemon
 * @copyright  
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    
 */

/**
 * @category   Concentre
 * @package    Concentre_Daemon
 * @copyright  
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
abstract class Concentre_Daemon_Abstract
{
    /**
     * User-Group of the daemon.
     *
     * @var int
     */
    protected $_groupID = 0;

    /**
     * Process-ID of the daemon.
     *
     * @var int
     */
    protected $_processID = 0;

    /**
     * User-ID of the daemon.
     *
     * @var int
     */
    protected $_userID = 0;

    /**
     * Zend Logger.
     *
     * @var Zend_Log
     */
    protected $_logger;

    /**
     * Contains the default options.
     *
     * @var array
     */
    protected  $_defaultOptions = array();

    /**
     * Contains the options set by user.
     *
     * @var array
     */
    protected $_options = array(); 

    /**
     * Constructor
     *
     * @param array $options
     * @return void
     */
    public function __construct($options = array())
    {
        if (! extension_loaded('pcntl')
        or  ! extension_loaded('posix') ) {
            require_once 'Concentre/Daemon/Exception.php';
            throw new Concentre_Daemon_Exception('PCNTL and POSIX extensions are required.');
        }

        $this->setOptions($options);
        $this->init();
    }

    /**
     * Initialize object
     *
     * Called from {@link __construct()} as final step of object instantiation.
     *
     * @return void 
     */
    public function init()
    {
    }

    /**
     * setOptions
     *
     * @param array $options
     * @return Concentre_Daemon_Abstract
     */
    public function setOptions($options)
    {
        if (! is_array($options) ) {
            if ($options instanceof Zend_Config) {
                $options = $options->toArray();
            } else {
                require_once 'Concentre/Daemon/Exception.php';
                throw new Concentre_Daemon_Exception('$options must be an array or Zend_Config object.');
            }
        }

        foreach ($options as $name => $value) {
            if (! array_key_exists($name, $this->_defaultOptions) ) {
                require_once 'Concentre/Daemon/Exception.php';
                throw new Concentre_Daemon_Exception("Unknown Concentre_Daemon option: {$name}");
            }

            $this->_options[$name] = $value;
        }

        return $this;
    }

    /**
     * setGroupId
     *   
     * @todo maybe some security checks
     * @param int $groupID
     * @return self
     */
    public function setGroupID($groupID)
    {
        $this->_groupID = $groupID;
        return $this;
    }

    /**
     * setUserId
     *
     * @todo maybe some security checks
     * @param int $userID
     * @return self
     */
    public function setUserID($userID)
    {
        $this->_userID = $userID;
        return $this;
    }

    /**
     * Start
     *
     * @return void
     */
    public function start()
    {
        // daemonize
        $this->_daemonize();

        // processing
        $this->_isRunning = true;

        while ($this->_isRunning) {
            $this->_process();
            sleep(1);
        }
    }

    /**
     * Stop
     *
     * @return void
     */
    public function stop()
    {
        $this->_isRunning = false;
    }

    /**
     * Forks and daemonize the main process.
     *
     * @return void
     */
    protected function _daemonize()
    {
        // already a daemon
        if ( posix_getppid() ) {
            return;
        }

        // set identity
        if (! posix_setgid($this->_groupID)
        or  ! posix_setuid($this->_userID) ) {
            die('Could not set identity.');
        }

        // register our signal handler
        pcntl_signal( SIGCHLD, array($this, '_signalHandler') );
        pcntl_signal( SIGUSR1, array($this, '_signalHandler') );
        pcntl_signal( SIGALRM, array($this, '_signalHandler') );

        // fork off the parent process
        $pid = pcntl_fork();

        if (0 > $pid) {
            die('Could not fork off a new process.');
        }

        if (0 < $pid) {
            exit(0);
        }

        // now we are executing as child process
        $parent = posix_getppid();

        // cancel certain signals
        pcntl_signal(SIGCHLD, SIG_DFL); # a child process dies
        pcntl_signal(SIGTSTP, SIG_IGN); # various TTY signals
        pcntl_signal(SIGTTOU, SIG_IGN);
        pcntl_signal(SIGTTIN, SIG_IGN);
        pcntl_signal(SIGHUP, SIG_IGN); # ignore hangup signal
        pcntl_signal(SIGTERM, SIG_DFL); # die on SIGTERM

        // change file-mode mask
        umask(0);

        // detach from terminal
        if ( -1 == posix_setsid() ) {
            die('Could not detach from terminal.');
        }

        // change working directory to prevent from being locked
        if (!@ chdir('/') ) {
            die('Could not change the working directory.');
        }

        // tell daddy that we are okay
        posix_kill($parent, SIGUSR1);
    }

    /**
     * 
     *
     * @param string $message
     * @param int $priority
     * @return void
     */
    protected function _log($message, $priority = Zend_Log::ERR)
    {
        if (! $this->_logger instanceof Zend_Log) {
            require_once 'Zend/Log.php';
            $this->_logger = new Zend_Log();
        }

        $this->_logger->log($message, $priority);
    }

    /**
     *
     *
     * @return void
     */
    protected function _signalHandler($signal)
    {
        switch($signal)
        {
            case SIGCHLD:
            case SIGUSR1:
            case SIGALRM:
            default:
                exit(0);
                break;
        }
    }
    
    /**
     *
     *
     * @return void
     */
    abstract protected function _process();
}
